﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace TfsBacklogMgr.Main
{
    [NotifyPropertyChanged]
    public class WorkItem : ViewModelBase 
    {


        #region Factory

        protected static Dictionary<int, WorkItem> _cache = new Dictionary<int, WorkItem>();

        public static WorkItem GetWorkItem(int id)
        {
            if (_cache.ContainsKey(id))
                return _cache[id];
            else
                return null;
        }

        public static void Add(WorkItem newItem)
        {
            if (!_cache.ContainsKey(newItem.Id))
                _cache[newItem.Id] = newItem;
            else
            {
                _cache.Remove(newItem.Id);
                _cache[newItem.Id] = newItem;
            }
        }

        public static void SaveAll()
        {
            foreach (var item in _cache)
            {
                item.Value.Save();
            }
        }

        #endregion

        private int _StackRank;
        private User _AssignedTo;
        private WorkItem _Parent;
        private ObservableCollection<WorkItem> _Children;
        private Iteration _Iteration;
        private ObservableCollection<string> _States;
        private Dictionary<string, string> _Reasons;
        Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem _wi;
        dynamic _item;

        public WorkItem( dynamic workitem )
        {
            _item = workitem;
            _Children = new ObservableCollection<WorkItem>();
            _States = new ObservableCollection<string>();
            _Reasons = new Dictionary<string, string>();

            _States.Add(_item.State);
            var states = Context.GetContext().GetNextStatesFor(_item.Type.Name, _item.State);
            foreach (var state in states)
            {
                _States.Add(state.State);
                _Reasons[state.State] = state.Reason;
            }

            string username = workitem.Fields["System.AssignedTo"].Value;
            _AssignedTo = User.GetUser(username);

            string iter = workitem.IterationPath;
            if( ! string.IsNullOrEmpty(Context.GetContext().TfsProject) )
                iter = iter.Replace(Context.GetContext().TfsProject, "$");
            IterationPath = iter;

            //_wi.Fields[Microsoft.TeamFoundation.WorkItemTracking.Client.CoreField.AssignedTo].Value = "foo";
            //_wi.WorkItemLinks[0].LinkTypeEnd.Name
            //_wi.WorkItemLinks[0].TargetId

        }

        public int Id {
            get { return _item.Id; }
        }

        public string WIT
        {
            get { return _item.Type.Name; }
        }

        public int StackRank
        {
            get { return _StackRank; }
            set { _StackRank =  value; }
        }

        public ObservableCollection<WorkItem> Children
        {
            get
            {
                return _Children;
            }
        }

        public bool IsInBacklog
        {
            get { return _Iteration.IsBacklogIteration; }
        }

        public Iteration Iteration
        {
            get { return _Iteration; }
            set { _Iteration = value; _item.IterationPath = _Iteration.TfsPath; OnPropertyChanged("IterationPath"); OnPropertyChanged("IsInParentIteration"); OnPropertyChanged("IsInBacklog"); }
        }

        public string IterationPath {
            get
            {
                if (_Iteration == null)
                    return "";
                return _Iteration.Path;
            }
            set
            {
                Iteration newIter = Iteration.GetIteration(value);
                ChangeToIteration(newIter);
                OnPropertyChanged("IterationPath");
                OnPropertyChanged("IsInParentIteration");
            }
        }

        public bool IsInParentIteration
        {
            get
            {
                if( Parent == null )
                    return true;
                if (Iteration != Parent.Iteration)
                    return false;
                return true;
            }
        }
        public string AreaPath 
        {
            get { return _item.AreaPath; }
            set { _item.AreaPath = value; }
        }
        public string Title
        {
            get { return _item.Title; }
            set { _item.Title = value; }
        }

        public string Description
        {
            get { return  _item.Description; }
            set { _item.Description = value; }
        }

        public string State
        {
            get { return _item.State; }
            set // Aspect ensures that only changes are passed thru
            {
                _item.State = value;
                if( _Reasons.ContainsKey(value) )
                    Reason = _Reasons[value];
                OnPropertyChanged("IsClosed");
                OnPropertyChanged("Points", "TotalPoints");
                OnPropertyChanged("Hours",  "TotalHours");
                OnPropertyChanged("Remain", "TotalRemain");
            }
        }
        public bool IsClosed
        {
            get { return _item.State != null && _item.State.ToLowerInvariant() == "closed"; }
        }

        public ObservableCollection<string> States
        {
            get { return _States; }
        }


        public string Reason
        {
            get { return _item.Reason; }
            set { _item.Reason = value; }
        }

        private double GetNumberField(string fieldname)
        {
            if (IsClosed)
                return 0;
            else
                if ( ! string.IsNullOrEmpty(fieldname) &&
                    _item.Fields.Contains(fieldname) )
                    return (double)_item.Fields[fieldname].Value;
                else
                    return 0;
        }

        private void SetNumberField(string fieldname, double num)
        {
            if (IsClosed)
                return;
            else
                if (!string.IsNullOrEmpty(fieldname) &&
                    _item.Fields.Contains(fieldname) )
                    _item.Fields[fieldname].Value = num;
        }

        public double Points
        {
            get 
            {
                return GetNumberField(Context.GetContext().WITF_Points);
            }
            set 
            {
                SetNumberField(Context.GetContext().WITF_Points, value);
                if (_AssignedTo != null)
                    _AssignedTo.UpdateTotals();
                OnPropertyChanged("TotalPoints");
            }
        }

        public double Hours
        {
            get
            {
                return GetNumberField(Context.GetContext().WITF_Hours);
            }
            set
            {
                SetNumberField(Context.GetContext().WITF_Hours, value);
                if (_AssignedTo != null)
                    _AssignedTo.UpdateTotals();
                OnPropertyChanged("TotalHours");
            }
        }

        public double Remain
        {
            get
            {
                return GetNumberField(Context.GetContext().WITF_Remain);
            }
            set
            {
                SetNumberField(Context.GetContext().WITF_Remain, value);
                if (_AssignedTo != null) 
                    _AssignedTo.UpdateTotals();
                OnPropertyChanged("TotalRemain");
            }
        }

        public double TotalHours
        {
            get
            {
                double sum = Hours;
                foreach (var item in _Children)
                    sum += item.Hours;
                return sum;
            }
        }

        public double TotalRemain
        {
            get
            {
                double sum = Remain;
                foreach (var item in _Children)
                    sum += item.Remain;
                return sum;
            }
        }

        // 0 --> "{0:0}"
        // 1 --> "{0:0.0}"
        // 2 --> "{0:0.00}"
        private string GetFormat(int precision)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("F");
            sb.Append(precision);
            return sb.ToString();
        }

        public string PointsFormat { get { return GetFormat(Context.GetContext().WITF_Points_Precision); } }
        public string HoursFormat { get { return GetFormat(Context.GetContext().WITF_Hours_Precision); } }
        public string RemainFormat { get { return GetFormat(Context.GetContext().WITF_Remain_Precision); } }

        public System.Windows.Visibility PointsVisibility { get { return Context.GetContext().WITF_Points.Length > 0 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; } }
        public System.Windows.Visibility HoursVisibility { get { return Context.GetContext().WITF_Hours.Length > 0 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; } }
        public System.Windows.Visibility RemainVisibility { get { return Context.GetContext().WITF_Remain.Length > 0 ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; } }


        public User AssignedTo 
        { 
            get
            {
                return _AssignedTo;
            }
            set
            {
                if( _AssignedTo != null )
                    _AssignedTo.RemoveWorkItem(this);
                _AssignedTo = value;
                if (_AssignedTo != null)
                {
                    dynamic assignTo = _item.Fields["System.AssignedTo"];
                    assignTo.Value = value.Name;
                    _AssignedTo.AddWorkItem(this);
                }
            }
        }
        public WorkItem Parent
        {
            get
            {
                return _Parent;
            }
            set
            {
                if (_Parent != null)
                    _Parent.RemoveChild(this);
                _Parent = value;
                if (_Parent != null)
                {
                    foreach (var wil in _item.WorkItemLinks)
                    {
                        if (wil.LinkTypeEnd.Name.Equals("Parent") )
                        {
                            wil.TargetId = _Parent.Id;
                        }
                    }
                    _Parent.AddChild(this);
                }
            }
        }

        /// <summary>
        /// Change this and all child-items to the new iteration
        /// </summary>
        /// <param name="newIter"></param>
        internal void ChangeToIteration(Iteration newIter)
        {
            if (newIter != _Iteration)
            {
                if( _Iteration != null )
                    _Iteration.RemoveWorkItem(this);
                Iteration = newIter;
                if (_Iteration != null)
                    _Iteration.AddWorkItem(this);
                foreach (var i in _Children)
                {
                    if( ! i.IsClosed )
                        i.ChangeToIteration(newIter);
                }
            }
        }

        internal void RemoveChild(WorkItem workItem)
        {
            if( workItem != null )
            {
                _Children.Remove(workItem);
                workItem.Parent = null;
                workItem.PropertyChanged -= workItem_PropertyChanged;
            }
        }

        internal void AddChild(WorkItem workItem)
        {
            if (!_Children.Contains(workItem))
            {
                _Children.Add(workItem);
                if (workItem.Parent != this)
                    workItem.Parent = this;
                workItem.PropertyChanged += workItem_PropertyChanged;
                // make sure that children are not shown twice in the same iteration
                if( Iteration != null)
                    Iteration.RemoveChildrenOfWorkItem( this );
            }
        }

        void workItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "TotalPoints") OnPropertyChanged("TotalPoints");
            if (e.PropertyName == "TotalHours") OnPropertyChanged("TotalHours");
            if (e.PropertyName == "TotalRemain") OnPropertyChanged("TotalRemain");
        }

        public void Save()
        {
            if ( _item.IsDirty )
            {
                var valids = _item.Validate();
                bool isValid = _item.IsValid();
                if (isValid)
                    _item.Save();
                else
                {
                    foreach (object i in valids)
                        System.Diagnostics.Debug.WriteLine(i);
                }
            }
        }


        public override string ToString()
        {
            return string.Format("{0} {2} '{1}'", Id, Title, WIT);
        }

    }
}
